﻿using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using OpenTap.Plugins.Interfaces.Common;

namespace OpenTap.Plugins.Pm
{
    [Display("PmRsNrpZxx USB driver", Group: "OpenTap.Plugins", Description: "R&S NRP-x USB driver")]
    public class PmRsNrpZxx : PmBase
    {
        #region Settings        

        [Display("Automatic calibration state.",
            "If true, the automatic calibration will be executed in every 10 minutes")]
        public bool AutoCalibrationState { get; set; }

        #endregion

        public PmRsNrpZxx()
        {
            Name = "PmRsNrpZxx";
        }

        protected void Wait()
        {
            Log.Debug("PM:Wait");
            var bOperComplete = false;
            /* Wait about 3500 seconds for operation to complete to avoid infinite loop */
            for (var i = 0; i < 35000; i++)
            {
                var waiStatus = ScpiQuery("STAT:OPER:COND?\n");
                waiStatus = waiStatus.TrimEnd('\n');
                if (waiStatus.Equals("0"))
                {
                    bOperComplete = true;
                    break;
                }

                Thread.Sleep(100);
            }

            if (bOperComplete == false)
                throw new ApplicationException("Timeout when waiting for operation to complete!");
        }

        protected void SetMeasureFrequency(long frequencyHz)
        {
            if (MaxFrequencyHz < frequencyHz || MinFrequencyHz > frequencyHz)
                throw new ApplicationException("Frequency: " + frequencyHz + "Hz is out of range. Range is" +
                                               MinFrequencyHz +
                                               "Hz - " + MaxFrequencyHz + "Hz");
            ScpiCommand("SENS:FREQ " + frequencyHz + "\n");
            var res = ScpiQuery("SENS:FREQ?\n");
            Log.Info(res);
        }

        #region Overrides of PmBase
        /// <inheritdoc />
        public override void ExecuteZeroCompensation()
        {
            Log.Debug("PM:ExecuteZeroCompensation start");
            ScpiCommand("CAL:ZERO:AUTO ONCE");
            Opc(TimeOutOpcTypicSec);
            Log.Debug("PM:ExecuteZeroCompensation done. Status(" + ScpiQuery("CAL:ZERO:AUTO?") + ")");
            QueryErrWithExc(true, "ExecuteZeroCompensation: CAL:ZERO:AUTO ONCE");
        }

        /// <summary>
        ///     Open procedure for the instrument.
        /// </summary>
        public override void Open()
        {
            base.Open();

            Log.Debug("Pm: Open");
            QueryErrWithExc(false, "PM Initializing.");
            //ScpiCommand("*RST\n");
            Reset();
            var response = ScpiQuery("SYST:INFO? \"MAXFREQ\"\n");
            response = response.TrimStart('\"');
            var index = response.LastIndexOf('\"');
            if (index > 0)
                response = response.Substring(0, index);

            MaxFrequencyHz = long.Parse(response, NumberStyles.Float);
            response = ScpiQuery("SYST:INFO? \"MINFREQ\"\n");
            // Trim response for NRP
            response = response.TrimStart('\"');
            index = response.LastIndexOf('\"');
            if (index > 0)
                response = response.Substring(0, index);
            MinFrequencyHz = long.Parse(response, NumberStyles.Float);
            ScpiCommand("SENS:FUNC \"POW:AVG\"\n");
        }

        public override double MeasureBurstPower(long frequencyKHz)
        {
            Log.Debug("PM:MeasurePowerBurst start");

            ScpiCommand("TRIG:LEV " + 0.000020);
            ScpiCommand("SENS:FUNC \"POW:BURS:AVG\"\n");

            ScpiCommand("SENS:TIM:EXCL:STAR " + 0.0005);
            ScpiCommand("SENS:TIM:EXCL:STOP " + 0.0005);

            //SetTrigSource(ETrigSource.Internal);

            SetMeasureFrequency(frequencyKHz * 1000);
            ScpiCommand("INIT:IMM\n");
            Wait();
            var response = ScpiQuery("FETCH?\n");
            response = response.Substring(0,
                response.IndexOf("\n", StringComparison.InvariantCulture) == -1
                    ? response.Length
                    : response.IndexOf("\n", StringComparison.InvariantCulture) + 1);

            double power = 0;
            if (response.Split(',').Any(s => double.TryParse(s, NumberStyles.Float,
                CultureInfo.InvariantCulture, out power)))
            {
                return ConvertWattsToDBm(power);
            }
            //conversion not succeeded
            throw new ApplicationException("PM:Measure power result conversion not succeeded");
        }

        /// <inheritdoc />
        public override double MeasurePower(long frequencyKHz)
        {
            Log.Debug("PM:MeasurePower start");

            ScpiCommand("SENS:FUNC \"POW:AVG\"");

            SetMeasureFrequency(frequencyKHz * 1000);
            ScpiCommand("INIT:IMM\n");
            Wait();
            var response = ScpiQuery("FETCH?\n");
            response = response.Substring(0,
                response.IndexOf("\n", StringComparison.InvariantCulture) == -1
                    ? response.Length
                    : response.IndexOf("\n", StringComparison.InvariantCulture) + 1);

            double power = 0;
            if (response.Split(',').Any(s => double.TryParse(s, NumberStyles.Float,
                CultureInfo.InvariantCulture, out power)))
                return ConvertWattsToDBm(power);

            //conversion not succeeded
            throw new ApplicationException("PM:Measure power result conversion not succeeded");
        }

        public override void SetAverageAuto()
        {
            Log.Debug("PM:SetAverageAuto");

            ScpiCommand("SENS:AVER:COUN:AUTO ON\n");
            ScpiCommand("SENS:AVER:COUN:AUTO:TYPE NSR\n");
            ScpiCommand("SENS:AVER:COUN:AUTO:NSR " + 1 + "\n");
            ScpiCommand("SENS:AVER:COUN:AUTO:MTIM " + 500 + "\n");
            QueryErrWithExc(true, "PM:SetAverageAuto()");
        }

        /// <inheritdoc />
        public override void SetAverageOff()
        {
            ScpiCommand("SENS:AVER:STAT OFF"); // Disables averaging.
            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetAverageOff()");
        }

        /// <inheritdoc />
        public override void SetAverageManual(int averageCount)
        {
            if (averageCount < 1)
                throw new ArgumentException("PM:SetAverageManual Invalid parameter value(" + averageCount + ")",
                    nameof(averageCount));

            ScpiCommand("SENS:AVER:COUN:AUTO OFF\n");
            ScpiCommand("SENS:AVER:COUN " + averageCount + "\n");
            ScpiCommand("SENS:CORR:OFFS:STAT OFF\n");
            ScpiCommand("SENS:AVER:STAT ON\n");

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetAverageManual(" + averageCount + ")");
        }

        public override void SetOffSetValue(EState state, double offsetDb)
        {
            Log.Info("Pm:SetOffSetValue(dBm): " + offsetDb);
            const int lowLimit = -200;
            const int highLimit = 200;

            if (-200 > offsetDb || 200 < offsetDb)
            {
                var errMsg = Scpi.Format("Offset value: {0} out of limits: {1} and {2}", offsetDb, lowLimit,
                    highLimit);
                throw new ApplicationException(errMsg);
            }

            ScpiCommand("SENS:CORR:OFFS " + offsetDb);
            ScpiCommand(state == EState.On ? "SENS:CORR:OFFS:STAT ON\n" : "SENS:CORR:OFFS:STAT OFF\n");

            Opc(TimeOutOpcTypicSec);
            QueryErrWithExc(true, "PM:SetOffSetValue(" + state + ", " + offsetDb + ")");
        }

        public override void WaitForOperationComplete(int timeoutMs = 2000)
        {
            Log.Debug("*OPC? not support by NRP Z serial power meter");
        }

        #endregion

        private static double ConvertWattsToDBm(double watts)
        {
            if (watts <= 1e-13)
                watts = 1e-17; // = -140dBm (previous 1e-18 / -150dBm)
            return 10 * Math.Log10(watts * 1000);
        }
    }
}